/**************************************
*                                     *
*   Jeff Molofee's Basecode Example   *
*          nehe.gamedev.net           *
*                2001                 *
*                                     *
**************************************/

#include <windows.h>											// Header File For Windows
#include <cstdio>
#include "gl\glee.h"
#include <gl\gl.h>												// Header File For The OpenGL32 Library
#include <gl\glu.h>												// Header File For The GLu32 Library
#include <gl\glaux.h>											// Header File For The GLaux Library
#include "NeHeGL.h"												// Header File For NeHeGL
#include "picopng.h"											// Header file for PicoPNG - one file PNG lib
#include "textfile.h"

#include "fontfile.h"

#include <fcntl.h>
#include <cstdlib>
#include <io.h>

#include <vector>
#include <iostream>
#include <fstream>
#include <cassert>
#include <string>
#include <sstream>

#pragma comment( lib, "opengl32.lib" )							// Search For OpenGL32.lib While Linking
#pragma comment( lib, "glu32.lib" )								// Search For GLu32.lib While Linking
#pragma comment( lib, "glaux.lib" )								// Search For GLaux.lib While Linking
#pragma comment( lib, "glee.lib" )

#ifndef CDS_FULLSCREEN											// CDS_FULLSCREEN Is Not Defined By Some
#define CDS_FULLSCREEN 4										// Compilers. By Defining It This Way,
#endif															// We Can Avoid Errors

GL_Window*	g_window;
Keys*		g_keys;

// User Defined Variables
float		angle;												// Used To Rotate The Triangles
int			rot1, rot2;											// Counter Variables

GLenum		my_program;
std::vector<unsigned char> image;
GLuint		textureHandles[1];	
typedef std::vector<fontChar>::const_iterator fontCharIter;
std::vector<fontChar> fontChars_;
uint currentFontChar;

//x=382   y=250   width=35    height=46
float minS	=	382.0f/512.0f;
float minT	=	250.0f/512.0f;
float maxS	=	(382.0f+35.0f)/512.0f;
float maxT	=	(250.0f+46.0f)/512.0f;

void printShaderInfoLog(GLuint obj, char * filename)
{
	int infologLength = 0;
	int charsWritten  = 0;
	char *infoLog;

	glGetShaderiv(obj, GL_INFO_LOG_LENGTH,&infologLength);

	if (infologLength > 0)
	{
		infoLog = (char *)malloc(infologLength);
		glGetShaderInfoLog(obj, infologLength, &charsWritten, infoLog);
		printf("%s\n",infoLog);
		textFileWrite( filename, infoLog );
		free(infoLog);
	}
}

void printProgramInfoLog(GLuint obj, char * filename)
{
	int infologLength = 0;
	int charsWritten  = 0;
	char *infoLog;

	glGetProgramiv(obj, GL_INFO_LOG_LENGTH,&infologLength);

	if (infologLength > 0)
	{
		infoLog = (char *)malloc(infologLength);
		glGetProgramInfoLog(obj, infologLength, &charsWritten, infoLog);
		printf("%s\n",infoLog);
		textFileWrite( filename, infoLog );
		free(infoLog);
	}
}

void printErrorLog(std::string &errorText)
{
	textFileWrite( "error.log", (char*)errorText.c_str() );
}

void checkGlError()
{
	unsigned int lastError = glGetError();
	if(GL_NO_ERROR!=lastError)
	{
		std::stringstream logit; 
		logit << "checkGlError found this error: ";
		switch(lastError) 
		{
		case GL_INVALID_ENUM     :    logit << "GL_INVALID_ENUM" << std::endl; break;
		case GL_INVALID_VALUE    :    logit << "GL_INVALID_VALUE" << std::endl; break;
		case GL_INVALID_OPERATION:    logit << "GL_INVALID_OPERATION" << std::endl; break;
		case GL_STACK_OVERFLOW   :    logit << "GL_STACK_OVERFLOW" << std::endl; break;
		case GL_STACK_UNDERFLOW  :    logit << "GL_STACK_UNDERFLOW" << std::endl; break;
		case GL_OUT_OF_MEMORY    :    logit << "GL_OUT_OF_MEMORY" << std::endl; break;
		}
		logit << std::endl;
		printErrorLog( logit.str() );
	}
}

BOOL Initialize (GL_Window* window, Keys* keys)					// Any GL Init Code & User Initialiazation Goes Here
{
	g_window	= window;
	g_keys		= keys;

	// Start Of User Initialization
	angle		= 0.0f;											// Set Starting Angle To Zero

	// You can also query the OpenGL version:
	if (GLEE_VERSION_2_0)
	{
		//OpenGL 2.0 is supported 

		// Load the shader program
		{
			const char * my_source_vertex		= NULL;
			const char * my_source_fragment		= NULL;
			
			my_source_vertex	= textFileRead( "almag.vert" );
			my_source_fragment	= textFileRead( "almag.frag" );

			if ( my_source_vertex && my_source_fragment )
			{
				// found the shaders so move onto attaching them to a program
				my_program = glCreateProgram();
				// get handles for them
				GLenum		my_vertex_shader	= glCreateShader(GL_VERTEX_SHADER);
				GLenum		my_fragment_shader	= glCreateShader(GL_FRAGMENT_SHADER);

				glShaderSource(my_vertex_shader, 1, &my_source_vertex, NULL);
				glShaderSource(my_fragment_shader, 1, &my_source_fragment, NULL);
				checkGlError();
				glCompileShader(my_vertex_shader);
				glCompileShader(my_fragment_shader);
				checkGlError();
				glAttachShader(my_program, my_vertex_shader);
				glAttachShader(my_program, my_fragment_shader);
				checkGlError();

				// Link The Program Object
				glLinkProgram(my_program);
				checkGlError();

				// check for any error messages associated with the compilation or linking phase of building the shader
				// this is useful if you're playing with a new shader/making alterations etc and not getting much useful output.
				printShaderInfoLog(my_vertex_shader, "vertex.log");
				printShaderInfoLog(my_fragment_shader, "fragment.log");
				printProgramInfoLog(my_program, "program.log");

				delete(my_source_vertex);
				delete(my_source_fragment);

				my_source_vertex		= NULL;
				my_source_fragment		= NULL;

				// more error checking, I had some issues during the first few runs of this when I was writing it on my lunchbreak
				// hopefully you'll find things like this useful in your own code :(
				glValidateProgram(my_program);
				char chocolog[1025];
				int len;
				glGetProgramInfoLog(my_program, 1024, &len, chocolog);

				// Use The Program Object Instead Of Fixed Function OpenGL
				// NB: we don't really need to do this here but I'm setting it can checking for errors to catch anything
				// going wrong at load time rather than run time.
				glUseProgram(my_program);
				checkGlError();
			}
			else
			{
				// we failed to load the designated shaders
				std::stringstream logit; 
				logit << "we failed to load the designated shaders : " << "almag.vert" << " & " << "almag.frag" << std::endl;
				printErrorLog( logit.str() );
				return FALSE;
			}
		}// end-of shader loading

		// now load the texture
		unsigned int w, h;
		{
			//const char* filename = "FINALNEW.TTF_font.png";
			const char* filename = "VeraMoBI.ttf_font.png";

			//load and decode
			std::vector<unsigned char> buffer;
			loadFile(buffer, filename);
			unsigned error = decodePNG(image, w, h, buffer.empty() ? 0 : &buffer[0], (unsigned)buffer.size());

			//if there's an error, display it
			if(error != 0) 
			{
				// we failed to load the designated shaders
				std::stringstream logit; 
				logit << "decoding the png gave error: " << error << std::endl;
				printErrorLog( logit.str() );
				return FALSE;
			}

			//the pixels are now in the vector "image", use it as texture, draw it, ...
			if(image.size() > 4) 
			{
				std::stringstream logit; 
				logit << "width: " << w << " height: " << h << " first pixel: " << std::hex << unsigned(image[0]) << unsigned(image[1]) << unsigned(image[2]) << unsigned(image[3]) << std::endl;
				textFileWrite( "imageInfo.log", (char*)logit.str().c_str() );
			}

			glGenTextures(1, &textureHandles[0]);	// Create/Get a valid Texture Handle from OpenGL
			checkGlError();

			// Typical Texture Generation Using Data From The Bitmap
			glBindTexture(GL_TEXTURE_2D, textureHandles[0]);
			checkGlError();

			// These values could probably be found from the png decoder, but I'm writing this in my lunchtime dammit!
			glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, w, h, 0, GL_RGBA, GL_UNSIGNED_BYTE, &image[0]);
			checkGlError();

			glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
			glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
			glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
			glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
			glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
			glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE);
			checkGlError();
		}

		// Load the texture coords for the characters and other information about the font
		{
			parseFontFile( fontChars_, std::string("VeraMoBI.ttf_font.txt"), w, h );
			assert(fontChars_.size()>0);
			currentFontChar = 1;
			// one thing to note here is that minT and maxT are REVERSED, this is so that the texture isn't drawn upside-down ;)
			minS	=	fontChars_[currentFontChar].sMin_;
			maxT	=	fontChars_[currentFontChar].tMin_;
			maxS	=	fontChars_[currentFontChar].sMax_;
			minT	=	fontChars_[currentFontChar].tMax_;
		}
	}
	else
	{
		// we don't support GLEE_VERSION_2_0
		// though to be fair I only did this as a test to see if versioning was ok and I'd installed Glee correctly ;)
		return FALSE;
	}

	glClearColor (0.0f, 0.25f, 0.25f, 0.0f);					// Non-Black Background - don't use black, use a colour that something will show against!
	glClearDepth (1.0f);										// Depth Buffer Setup
	glDepthFunc (GL_LEQUAL);									// The Type Of Depth Testing (Less Or Equal)
	glEnable (GL_DEPTH_TEST);									// Enable Depth Testing
	glShadeModel (GL_SMOOTH);									// Select Smooth Shading
	glHint (GL_PERSPECTIVE_CORRECTION_HINT, GL_NICEST);			// Set Perspective Calculations To Most Accurate

	// this bit is very important, or we won't catch any wabbits!
	glEnable(GL_TEXTURE_2D);
	glDisable(GL_ALPHA_TEST);
	glEnable(GL_BLEND);
	glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);

	return TRUE;												// Return TRUE (Initialization Successful)
}

void Deinitialize (void)										// Any User DeInitialization Goes Here
{
}

void Update (DWORD milliseconds)								// Perform Motion Updates Here
{
	if ( keyPressed(VK_ESCAPE) )								// Is ESC Being Pressed?
	{
		TerminateApplication (g_window);						// Terminate The Program
	}

	if ( keyPressed(VK_F1) )									// Is F1 Being Pressed?
	{
		ToggleFullscreen (g_window);							// Toggle Fullscreen Mode
	}

	if ( keyPressed(VK_SPACE) )									// Is Space Being Pressed?
	{
		fontCharIter it=fontChars_.begin();
		++currentFontChar;
		if (currentFontChar>=fontChars_.size())
		{
			currentFontChar = 0;
		}
		// one thing to note here is that minT and maxT are REVERSED, this is so that the texture isn't drawn upside-down ;)
		minS	=	fontChars_[currentFontChar].sMin_;
		maxT	=	fontChars_[currentFontChar].tMin_;
		maxS	=	fontChars_[currentFontChar].sMax_;
		minT	=	fontChars_[currentFontChar].tMax_;
	}

	angle += (float)(milliseconds) / 25.0f;						// Update angle Based On The Clock
}

void Draw (void)
{
	glClear (GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);		// Clear Screen And Depth Buffer
	glLoadIdentity ();											// Reset The Modelview Matrix
	glTranslatef (0.0f, 0.0f, -6.0f);							// Translate 6 Units Into The Screen
	glRotatef (angle, 0.0f, 1.0f, 0.0f);						// Rotate On The Y-Axis By angle

	// less hacking please
	glUseProgram(my_program);
	// get the sampler, I could do this once, but I love it sooooo much that I do it every frame.
	int my_sampler_uniform_location = glGetUniformLocation(my_program, "textMap");
	checkGlError();
	// set the texture unit and bind it, then tell the sampler which unit (thats why we pass "0") the texture will be in.
	glActiveTexture(GL_TEXTURE0);
	glBindTexture(GL_TEXTURE_2D, textureHandles[0]);
	glUniform1i(my_sampler_uniform_location, 0);
	checkGlError();

	// yes your honour, this _WAS_ cut n' wasted from another tutorial!!!!11!eleven
	glBegin(GL_QUADS);
		// Front Face
		glTexCoord2f(minS, minT); glVertex3f(-1.0f, -1.0f,  1.0f);	// Bottom Left Of The Texture and Quad
		glTexCoord2f(maxS, minT); glVertex3f( 1.0f, -1.0f,  1.0f);	// Bottom Right Of The Texture and Quad
		glTexCoord2f(maxS, maxT); glVertex3f( 1.0f,  1.0f,  1.0f);	// Top Right Of The Texture and Quad
		glTexCoord2f(minS, maxT); glVertex3f(-1.0f,  1.0f,  1.0f);	// Top Left Of The Texture and Quad
		// Back Face
		glTexCoord2f(maxS, minT); glVertex3f(-1.0f, -1.0f, -1.0f);	// Bottom Right Of The Texture and Quad
		glTexCoord2f(maxS, maxT); glVertex3f(-1.0f,  1.0f, -1.0f);	// Top Right Of The Texture and Quad
		glTexCoord2f(minS, maxT); glVertex3f( 1.0f,  1.0f, -1.0f);	// Top Left Of The Texture and Quad
		glTexCoord2f(minS, minT); glVertex3f( 1.0f, -1.0f, -1.0f);	// Bottom Left Of The Texture and Quad
		// Top Face
		glTexCoord2f(minS, maxT); glVertex3f(-1.0f,  1.0f, -1.0f);	// Top Left Of The Texture and Quad
		glTexCoord2f(minS, minT); glVertex3f(-1.0f,  1.0f,  1.0f);	// Bottom Left Of The Texture and Quad
		glTexCoord2f(maxS, minT); glVertex3f( 1.0f,  1.0f,  1.0f);	// Bottom Right Of The Texture and Quad
		glTexCoord2f(maxS, maxT); glVertex3f( 1.0f,  1.0f, -1.0f);	// Top Right Of The Texture and Quad
		// Bottom Face
		glTexCoord2f(maxS, maxT); glVertex3f(-1.0f, -1.0f, -1.0f);	// Top Right Of The Texture and Quad
		glTexCoord2f(minS, maxT); glVertex3f( 1.0f, -1.0f, -1.0f);	// Top Left Of The Texture and Quad
		glTexCoord2f(minS, minT); glVertex3f( 1.0f, -1.0f,  1.0f);	// Bottom Left Of The Texture and Quad
		glTexCoord2f(maxS, minT); glVertex3f(-1.0f, -1.0f,  1.0f);	// Bottom Right Of The Texture and Quad
		// Right face
		glTexCoord2f(maxS, minT); glVertex3f( 1.0f, -1.0f, -1.0f);	// Bottom Right Of The Texture and Quad
		glTexCoord2f(maxS, maxT); glVertex3f( 1.0f,  1.0f, -1.0f);	// Top Right Of The Texture and Quad
		glTexCoord2f(minS, maxT); glVertex3f( 1.0f,  1.0f,  1.0f);	// Top Left Of The Texture and Quad
		glTexCoord2f(minS, minT); glVertex3f( 1.0f, -1.0f,  1.0f);	// Bottom Left Of The Texture and Quad
		// Left Face
		glTexCoord2f(minS, minT); glVertex3f(-1.0f, -1.0f, -1.0f);	// Bottom Left Of The Texture and Quad
		glTexCoord2f(maxS, minT); glVertex3f(-1.0f, -1.0f,  1.0f);	// Bottom Right Of The Texture and Quad
		glTexCoord2f(maxS, maxT); glVertex3f(-1.0f,  1.0f,  1.0f);	// Top Right Of The Texture and Quad
		glTexCoord2f(minS, maxT); glVertex3f(-1.0f,  1.0f, -1.0f);	// Top Left Of The Texture and Quad
	glEnd();

	glFlush();													// Flush The GL Rendering Pipeline
}
